package cz.data.common.mongo.manage;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.InsertOneResult;
import com.mongodb.client.result.UpdateResult;
import cz.data.common.mongo.CacheMongoClientBean;
import cz.data.common.mongo.utils.CriteriaUtils;
import cz.data.common.support.db.DbProperty;
import cz.data.common.support.db.core.DbColumn;
import cz.data.common.support.db.core.DbTable;
import cz.data.common.support.db.manage.Condition;
import cz.data.common.support.db.manage.DbManage;
import cz.data.common.support.db.manage.ManageDialect;
import cz.data.common.support.db.manage.ManageDialectRegistry;
import cz.data.common.support.metadata.JavaTypeEnum;
import org.bson.Document;
import org.bson.json.JsonObject;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

public class MongoManage implements DbManage {
    private final MongoClient mongoClient;
    private final ManageDialect dialect;

    public MongoManage(final String connectionString, DbProperty property) {
        this.mongoClient = CacheMongoClientBean.create(connectionString, property);
        this.dialect = ManageDialectRegistry.factory(property.getDbTypeEnum());
    }

    @Override
    public List<DbTable> getTables(String dbName, String schemaName) {
        MongoDatabase database = mongoClient.getDatabase(dbName);
        MongoIterable<String> collectionNames = database.listCollectionNames();
        List<DbTable> tables = new ArrayList<>();
        for (String next : collectionNames) {
            DbTable table = new DbTable();
            table.setDbName(dbName);
            table.setTableName(next);
            table.setTableComment(next);
            tables.add(table);
        }
        return tables;
    }

    @Override
    public List<DbColumn> getTableColumns(String dbName, String schemaName, String tableName) {
        MongoDatabase database = mongoClient.getDatabase(dbName);
        MongoCollection<Document> collection = database.getCollection(tableName);
        FindIterable<Document> iterable = collection.find().sort(new JsonObject("{\"_id\": -1}")).limit(1);
        List<DbColumn> columns = new ArrayList<>();
        for (Document document : iterable) {
            Set<String> keySet = document.keySet();
            int index = 1;
            for (String key : keySet) {
                DbColumn column = new DbColumn();
                Object value = document.get(key);
                JavaTypeEnum javaTypeEnum;
                if (value != null) {
                    String simpleName = value.getClass().getSimpleName();
                    javaTypeEnum = JavaTypeEnum.match(simpleName);
                    column.setNullable(false);
                } else {
                    javaTypeEnum = JavaTypeEnum.STRING;
                    column.setNullable(true);
                }
                column.setColName(key);
                column.setDataType(javaTypeEnum.getName());
                column.setColComment(key);
                column.setColKey("_id".equals(key));
                column.setColPosition(index++);
                columns.add(column);
            }
            break;
        }
        return columns;
    }

    @Override
    public boolean createTable(DbTable table, Properties properties) {
        try {
            MongoDatabase database = mongoClient.getDatabase(table.getDbName());
            database.createCollection(table.getTableName());
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean deleteTable(DbTable table) {
        try {
            MongoDatabase database = mongoClient.getDatabase(table.getDbName());
            database.getCollection(table.getTableName()).drop();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public boolean insertData(DbTable table, JSONObject record) {
        this.dialect.preProcessData(table, record);
        MongoDatabase database = mongoClient.getDatabase(table.getDbName());
        MongoCollection<Document> collection = database.getCollection(table.getTableName());
        InsertOneResult insertOneResult = collection.insertOne(new Document(record));
        return insertOneResult.wasAcknowledged();
    }

    @Override
    public boolean updateData(DbTable table, JSONObject record) {
        this.dialect.preProcessData(table, record);
        MongoDatabase database = mongoClient.getDatabase(table.getDbName());
        MongoCollection<Document> collection = database.getCollection(table.getTableName());
        List<DbColumn> columns = table.getColumns();
        Update update = new Update();
        Criteria criteria = new Criteria();
        boolean expression = false;
        for (DbColumn column : columns) {
            String key = column.getColName();
            Object value = record.get(key);
            if (Boolean.TRUE.equals(column.getColKey())) {
                Objects.requireNonNull(value, "修改缺少主键参数");
                criteria.and(key).is(value);
                expression = true;
            } else if (Objects.nonNull(value)) {
                update.set(key, value);
            }
        }
        Assert.isTrue(expression, "修改缺少主键参数");
        Query query = new Query(criteria);
        UpdateResult updateResult = collection.updateOne(query.getQueryObject(), update.getUpdateObject());
        return updateResult.wasAcknowledged();
    }

    @Override
    public boolean removeData(DbTable table, JSONObject record) {
        this.dialect.preProcessData(table, record);
        MongoDatabase database = mongoClient.getDatabase(table.getDbName());
        MongoCollection<Document> collection = database.getCollection(table.getTableName());
        List<DbColumn> columns = table.getColumns();
        Criteria criteria = new Criteria();
        boolean expression = false;
        for (DbColumn column : columns) {
            String key = column.getColName();
            Object value = record.get(key);
            if (Objects.nonNull(value)) {
                criteria.and(key).is(value);
                expression = true;
            }
        }
        Assert.isTrue(expression, "删除缺少主键参数");
        DeleteResult deleteResult = collection.deleteMany(new Query(criteria).getQueryObject());
        return deleteResult.wasAcknowledged();
    }

    @Override
    public IPage<JSONObject> getPageData(DbTable table, List<String> fields,
                                         List<Condition> conditions, Page<JSONObject> page) {
        MongoDatabase database = mongoClient.getDatabase(table.getDbName());
        MongoCollection<Document> collection = database.getCollection(table.getTableName());
        Criteria criteria = new Criteria();
        for (Condition condition : conditions) {
            if (CriteriaUtils.isNotBlank(condition.getValue())) {
                JSONObject params = new JSONObject();
                params.put(condition.getColumn(), condition.getValue());
                this.dialect.preProcessData(table, params);
                Object value = params.get(condition.getColumn());
                condition.setValue(value);
            }
            if (CriteriaUtils.isNotBlank(condition.getLeftValue())) {
                JSONObject params = new JSONObject();
                params.put(condition.getColumn(), condition.getLeftValue());
                this.dialect.preProcessData(table, params);
                Object value = params.get(condition.getColumn());
                condition.setLeftValue(value);
            }
            if (CriteriaUtils.isNotBlank(condition.getRightValue())) {
                JSONObject params = new JSONObject();
                params.put(condition.getColumn(), condition.getRightValue());
                this.dialect.preProcessData(table, params);
                Object value = params.get(condition.getColumn());
                condition.setRightValue(value);
            }
            CriteriaUtils.builder(criteria, condition);
        }
        FindIterable<Document> findIterable = collection.find(new Query(criteria).getQueryObject())
                .skip((int) page.offset())
                .limit((int) page.getSize())
                .sort(new JsonObject("{\"_id\": -1}"));
        List<JSONObject> records = new ArrayList<>();
        for (Document document : findIterable) {
            records.add(new JSONObject(document));
        }
        page.setRecords(records);
        return page;
    }

    @Override
    public JSONObject getDataById(DbTable table, JSONObject params) {
        this.dialect.preProcessData(table, params);
        MongoDatabase database = mongoClient.getDatabase(table.getDbName());
        MongoCollection<Document> collection = database.getCollection(table.getTableName());
        List<DbColumn> columns = table.getColumns();
        Criteria criteria = new Criteria();
        boolean expression = false;
        for (DbColumn column : columns) {
            String key = column.getColName();
            Object value = params.get(key);
            if (Objects.nonNull(value)) {
                criteria.and(key).is(value);
                expression = true;
            }
        }
        Assert.isTrue(expression, "删除缺少主键参数");
        FindIterable<Document> findIterable = collection.find(new Query(criteria).getQueryObject()).limit(1);
        JSONObject record = null;
        for (Document document : findIterable) {
            record = new JSONObject(document);
            break;
        }
        return record;
    }

    @Override
    public void close() throws Exception {
//        this.mongoClient.close();
    }
}
